﻿using GetThis_Server.Users;
using Server;
using Server.EventHandlers;
using System;
using System.Collections.Concurrent;
using System.Diagnostics.Contracts;
using System.Text;

namespace GetThis_Server
{
    internal class ServerManager
    {
        #region Fields

        private readonly ConcurrentDictionary<TcpClientManager, AuthenticationManager> m_authenticationManagers;

        private readonly TcpServer m_tcpServer;

        private readonly Data m_data;

        private readonly Action<string> m_logMethod;

        private readonly Action m_stopServerMethod;

        #endregion

        #region Constructors

        internal ServerManager(TcpServer tcpServer, Data data, Action<string> logMethod, Action stopServerMethod)
        {
            Contract.Requires<ArgumentNullException>(tcpServer != null);
            Contract.Requires<ArgumentNullException>(data != null);
            Contract.Requires<ArgumentNullException>(logMethod != null);
            Contract.Requires<ArgumentNullException>(stopServerMethod != null);

            m_authenticationManagers = new ConcurrentDictionary<TcpClientManager, AuthenticationManager>();
            m_tcpServer = tcpServer;
            m_data = data;
            m_logMethod = logMethod;
            m_stopServerMethod = stopServerMethod;
            m_tcpServer.OnConnectionServerError += tcpServer_OnConnectionServerError;
            m_tcpServer.OnConnectionServerClientConnected += tcpServer_OnConnectionServerClientConnected;
            m_tcpServer.OnTcpServerError += tcpServer_OnTcpServerError;
            m_tcpServer.OnClientManagerError += tcpServer_OnClientManagerError;
            m_tcpServer.OnTcpServerClientCreated += tcpServer_OnTcpServerClientCreated;
            m_tcpServer.OnTcpServerClientDisposed += tcpServer_OnTcpServerClientDisposed;
            m_tcpServer.OnPacketReceived += tcpServer_OnPacketReceived;
            m_tcpServer.OnPacketSent += tcpServer_OnPacketSent;
        }

        #endregion

        #region Events

        private void tcpServer_OnConnectionServerError(ConnectionServer sender, ExceptionEventArgs e)
        {
            m_logMethod(String.Format("Erreur du serveur de connection : {0}", e != null ? e.Exception.Message : "???"));
            m_stopServerMethod();
        }

        private void tcpServer_OnConnectionServerClientConnected(ConnectionServer sender, TcpClientEventArgs e)
        {
            m_logMethod("Client connecté au serveur de connection");
        }

        private void tcpServer_OnTcpServerError(TcpServer sender, ExceptionEventArgs e)
        {
            m_logMethod(String.Format("Erreur du serveur : {0}", e != null ? e.Exception.Message : "???"));
            m_stopServerMethod();
        }

        private void tcpServer_OnClientManagerError(TcpClientManager sender, ExceptionEventArgs e)
        {
            var sb = new StringBuilder("Erreur du client (");
            if (sender == null)
                sb.Append("???");
            else
            {
                sb.Append(sender.Ip);
                sender.Dispose();
            }
            sb.AppendFormat(") : {0}", e != null ? e.Exception.Message : "???");
            m_logMethod(sb.ToString());
        }

        private void tcpServer_OnTcpServerClientCreated(TcpServer sender, TcpClientManagerEventArgs e)
        {
            if (e == null)
            {
                m_logMethod("Client inconnu connecté au serveur");
                m_stopServerMethod();
                return;
            }
            m_logMethod(String.Format("Client connecté au serveur : {0}", e.TcpClientManager.Ip));
            if (!m_authenticationManagers.TryAdd(e.TcpClientManager, new AuthenticationManager(e.TcpClientManager, m_data)))
            {
                m_logMethod("Impossible d'associer le client au gestionnaire d'authentification");
                m_stopServerMethod();
            }
        }

        private void tcpServer_OnTcpServerClientDisposed(TcpServer sender, TcpClientManagerEventArgs e)
        {
            if (e == null)
            {
                m_logMethod("Client inconnu déconnecté du serveur");
                m_stopServerMethod();
                return;
            }
            m_logMethod("Client déconnecté du serveur");
            AuthenticationManager authenticationManager;
            m_authenticationManagers.TryRemove(e.TcpClientManager, out authenticationManager);
        }

        private void tcpServer_OnPacketReceived(TcpClientManager sender, PacketEventArgs e)
        {
            if (e == null)
            {
                m_logMethod("Packet inconnu reçu");
                m_stopServerMethod();
                return;
            }
            AuthenticationManager authenticationManager;
            if (sender == null || !m_authenticationManagers.TryGetValue(sender, out authenticationManager))
            {
                m_logMethod(String.Format("Packet reçu d'un client inconnu : {0}", e.Packet.ToString()));
                m_stopServerMethod();
                return;
            }
            m_logMethod(String.Format("Le client {0} a envoyé le paquet : {1}", sender.Ip, e.Packet.ToString()));
            try
            {
                authenticationManager.Handle((dynamic)e.Packet);
            }
            catch (SystemException ex)
            {
                m_logMethod(String.Format("Erreur lors de la gestion du paquet : {0}", ex.Message));
            }
        }

        private void tcpServer_OnPacketSent(TcpClientManager sender, PacketEventArgs e)
        {
            if (e == null)
            {
                m_logMethod("Packet inconnu envoyé");
                m_stopServerMethod();
                return;
            }
            if (sender == null)
            {
                m_logMethod(String.Format("Packet envoyé à un client inconnu : {0}", e.Packet.ToString()));
                m_stopServerMethod();
                return;
            }
            m_logMethod(String.Format("Packet envoyé au client ({0}) : {1}", sender.Ip, e.Packet.ToString()));
        }

        #endregion

        #region Methods

        /// <summary>
        /// Define the invariant valid state for this class.
        /// </summary>
        [ContractInvariantMethod]
        private void InvariantValidState()
        {
            Contract.Invariant(m_authenticationManagers != null);
            Contract.Invariant(m_data != null);
            Contract.Invariant(m_logMethod != null);
            Contract.Invariant(m_stopServerMethod != null);
        }

        #endregion
    }
}
